Shiny is an excellent tool for visual exploration - it is at its most useful when a user can see something change before their eyes according to some selections. This is a great way to allow users to explore a dataset, explore the results of some analyses according to different parameters, and so on!
Let’s now add a plot to our Shiny app, to visualize the distribution of a variable depending on user input. We’ll be adding the ggplot2 and ggridges packages in the set-up step at the top of our app.R to allow us to make a plot.
# load packages
library(shiny)
library(ggridges)
library(ggplot2)
library(here)
library(readr)
To add a plot in our Shiny, we need to indicate where the plot should appear in the app. We can do this with plotOutput(), a similar function to tableOutput() in the previous section that is meant for plot outputs, as the name suggests.
# Define UI for application that makes a table andplots the Volcano Explosivity
# Index for the most eruptive volcanoes within a selected range of years
ui <- fluidPage(
# Application title ----
titlePanel("Exploring volcano explosivity"),
# Input interface ----
sidebarLayout(
sidebarPanel(
# Sidebar with a slider range input
sliderInput("years", # the id your server needs to use the selected value
label = h3("Years"),
min = 1900, max = 2020, # maximum range that can be selected
value = c(2010, 2020) # this is the default slider position
)
)
),
# Show the outputs from the server ---------------
mainPanel(
# Show a ridgeplot of explosivity index for selected volcanoes
plotOutput("ridgePlot"),
# then, show the table we made in the previous step
tableOutput("erupt_table")
)
)
Now our Shiny app knows where we want to place our plot.
We now need to create the plot we want to show in our app. This plot will change depending on one or several reactive values that the user can input or select in our UI.
We link the UI and server together with IDs that are assigned to each object. Above, we told the UI to expect a plot output with the ID "ridgePlot". In the server, we will create a plot and render it as a plot object using renderPlot(), and we will assign this plot output to the ID we call in the UI (as output$ridgePlot).
# Define server logic required to make your output(s)
server <- function(input, output) {
# prepare the data
# ----------------------------------------------------------
# read the dataset
eruptions <- readr::read_rds(here::here("data", "eruptions.rds"))
# filter the dataset to avoid overloading the plot
eruptions <- eruptions[which(eruptions$volcano_name %in% names(which(table(eruptions$volcano_name) > 30))),]
# this subsets to volcanoes that have erupted more than 30 times
# make reactive dataset
# ----------------------------------------------------------
# subset volcano data with input year range
eruptions_filtered <- reactive({
subset(eruptions, start_year >= input$years[1] & end_year <= input$years[2])
})
# create and render the outputs
# ----------------------------------------------------------
# create the table of volcanoes
output$erupt_table <- renderTable({
head(eruptions_filtered())
})
# render the plot output
output$ridgePlot <- renderPlot({
# create the plot
ggplot(data = eruptions_filtered(),
aes(x = vei,
y = volcano_name,
fill = volcano_name)) +
# we are using a ridgeplot geom here, from the ggridges package
geom_density_ridges( size = .5) + # line width
# label the axes
labs(x = "Volcano Explosivity Index", y = "") +
# adjust the ggplot theme to make the plot "prettier"
theme_classic() +
theme(legend.position = "none",
axis.text = element_text(size = 12, face = "bold"),
axis.title = element_text(size = 14, face = "bold"))
})
}
Now, if we run the Shiny app, we have a plot above the table we made previously. They are positioned in this way because the plotOutput() comes before the tableOutput() in the UI.
# Run the application
shinyApp(ui = ui, server = server)
We’ve now seen the basic building blocks of a Shiny app:
The user interface and server communicate through IDs that we assign to inputs from the user and outputs from the server.
We use an ID (in orange) to link the user input in the UI to the reactive values used in the server:
We use another ID (in blue) to link the output created in the server to the output shown in the user interface:
These elements can all be placed in one script named app.R or separately in scripts named ui.R and server.R. The choice is up to you, although it becomes easier to work in separate ui.R and server.R scripts when the Shiny app becomes more complex.
Example 1: Everything in app.R
Example 2: Split things into
ui.R and server.R
If you’d like to go one step further, you can also customize the appearance of your Shiny app using built-in themes, or creating your own themes.
There are several built-in themes in Shiny, which allow you to quickly change the appearance of your app. You can browse a gallery of available themes here here, or test themes out interactively here.
Let’s try the darkly theme on our Shiny app. To do this, we will need the shinythemes package.
library(shinythemes)
We can change the theme of our previous app with one line of code:
# Define UI for application that makes a table andplots the Volcano Explosivity
# Index for the most eruptive volcanoes within a selected range of years
ui <- fluidPage(
# Application title ----
titlePanel("Exploring volcano explosivity"),
# Input interface ----
sidebarLayout(
sidebarPanel(
# Sidebar with a slider range input
sliderInput("years", # the id your server needs to use the selected value
label = h3("Years"),
min = 1900, max = 2020, # maximum range that can be selected
value = c(2010, 2020) # this is the default slider position
)
)
),
# Show the outputs from the server ---------------
mainPanel(
# Show a ridgeplot of explosivity index for selected volcanoes
plotOutput("ridgePlot"),
# then, show the table we made in the previous step
tableOutput("erupt_table")
),
# Customize the theme ----------------------
# Use the darkly theme
theme = shinythemes::shinytheme("darkly")
)
Now, if we run the app, it looks a little different:
You can also go beyond the built-in themes, and create your own custom theme with the fonts and colours of your choice. You can also apply this theme to the outputs rendered in the app, to bring all the visuals together for a more cohesive look.
To create a custom theme, we will be using the bs_theme() function from the bslib package.
library(bslib)
# Create a custom theme
cute_theme <- bslib::bs_theme(
bg = "#36393B", # background colour
fg = "#FFD166", # most of the text on your app
primary = "#F26430", # buttons, ...
# you can also choose fonts
base_font = font_google("Open Sans"),
heading_font = font_google("Open Sans")
)
To apply this theme to our Shiny app (and the outputs), we will be using the thematic package.
library(thematic)
There are two essential steps to apply a custom theme to a Shiny app:
cute_theme).# Activate thematic
# so your R outputs will be changed to match up with your chosen styling
thematic::thematic_shiny()
# Define UI for application that makes a table andplots the Volcano Explosivity
# Index for the most eruptive volcanoes within a selected range of years
ui <- fluidPage(
# Application title ----
titlePanel("Exploring volcano explosivity"),
# Input interface ----
sidebarLayout(
sidebarPanel(
# Sidebar with a slider range input
sliderInput("years", # the id your server needs to use the selected value
label = h3("Years"),
min = 1900, max = 2020, # maximum range that can be selected
value = c(2010, 2020) # this is the default slider position
)
)
),
# Show the outputs from the server ---------------
mainPanel(
# Show a ridgeplot of explosivity index for selected volcanoes
plotOutput("ridgePlot"),
# then, show the table we made in the previous step
tableOutput("erupt_table")
),
# Customize the theme ----------------------
# Use our custom theme
theme = cute_theme
)
Now, if we run the app, the user interface and plot theme is set to the colours and fonts we set in cute_theme:
Here, thematic is not changing the colours used to represent a variable in our plot, because this is an informative colour scale (unlike the colour of axis labels, lines, and the plot background). However, if we remove this colour variable in our ridgeplot in the server, thematic will change the plot colours as well. Here is a simplified example of our server to see what these changes would look like:
# Define server logic required to make your output(s)
server <- function(input, output) {
#... (all the good stuff we wrote above)
# render the plot output
output$ridgePlot <- renderPlot({
# create the plot
ggplot(data = eruptions_filtered(),
aes(x = vei,
y = volcano_name)) + # we are no longer setting
# the fill argument to a variable
# we are using a ridgeplot geom here, from the ggridges package
geom_density_ridges(size = .5) +
# label the axes
labs(x = "Volcano Explosivity Index", y = "") +
# remove the "classic" ggplot2 so it doesn't override thematic's changes
# theme_classic() +
theme(legend.position = "none",
axis.text = element_text(size = 12, face = "bold"),
axis.title = element_text(size = 14, face = "bold"))
})
}
Now, our plot’s theme follows the app’s custom theme as well: